﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows.Shell;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace VlcWebTvN8
{
    public class WebTvServer:INotifyPropertyChanged
    {
        public WebTvServer()
        {
            ListeningPort = 1235;
            DomainName = "dyndns.casa-del-stifler.de";
            DreamboxHost = "dm800";
            //TranscodeProfile = "transcode{vcodec=h264,vb=512,fps=25,width=320,acodec=mp4a,ab=96,channels=2,samplerate=44100}";
            TranscodeProfile = "transcode{vcodec=h264,vb=384,fps=25,width=320,acodec=mp4a,ab=96,channels=2,samplerate=44100}";
            VLCPath = @"c:\Program Files (x86)\VideoLAN\VLC\vlc.exe";
            VLCPort = 65000 - Process.GetCurrentProcess().Id;
        }

        private int ListeningPort{get; set;}
        private string DomainName { get; set; }
        private string TranscodeProfile { get; set; }
        private string DreamboxHost { get; set; }
        private string VLCPath { get; set; }
        private int VLCPort { get; set; }
        Thread server;
        HttpListener httpListener;


        private void SetDouble(byte[] buffer, double value, int startIndex)
        {
            byte[] dBuf = BitConverter.GetBytes(value);

            for (int i = 0; i < sizeof(double); i++)
            {
                if (BitConverter.IsLittleEndian)
                {
                    buffer[startIndex + i] = dBuf[sizeof(double) - i - 1];
                }
                else
                {
                    buffer[startIndex + i] = dBuf[i];
                }
            }
        }

        private int GetStartIndex(string strHeader, string value)
        {
            var startIndex = strHeader.IndexOf(value);
            startIndex += value.Length + 1;
            return startIndex;
        }

        private double GetDouble(byte[] buffer, int startIndex)
        {
            if (BitConverter.IsLittleEndian)
            {
                byte[] dBuf = new byte[sizeof(double)];
                for (int i = 0; i < sizeof(double); i++)
                {
                    dBuf[sizeof(double) - i - 1] = buffer[startIndex + i];
                }
                return BitConverter.ToDouble(dBuf, 0);
            }

            return BitConverter.ToDouble(buffer, startIndex);
        }

        private bool _running;

        private long _bytesSent;
        public long BytesSent
        {
            get { return _bytesSent; }
            set { _bytesSent = value; NotifyPropertyChanged("BytesSent"); }
        }

        private ServerState _state;
        public ServerState State
        {
            get { return _state; }
            set { _state = value; NotifyPropertyChanged("State"); }
        }

        public void Start()
        {
            _running = true;
            
            server = new Thread(RunServer);
            server.Start();
        }

        public void Stop()
        {
            _running = false;
            httpListener.Close();
            server.Abort();
        }

        private void RunServer()
        {
            HttpListener httpListener;
            httpListener = new HttpListener();
            httpListener.Prefixes.Add("http://*:" + ListeningPort + "/");
            httpListener.Start();
            while (_running)
            {
                var context = httpListener.GetContext();
                Debug.Print(context.Request.Url.ToString());
                if (context.Request.Url.AbsolutePath == "/webtv.rss")
                {
                    ServeRSS(context);
                }
                else if (context.Request.Url.AbsolutePath.EndsWith(".jpg"))
                {
                    ServeImage(context);
                }
                else
                {
                    ServeVideo(context);
                }
            }
        }

        private void ServeImage(HttpListenerContext context)
        {
            var filePath="picon/jpg/" + context.Request.Url.AbsolutePath.Replace("/", "").Replace("_.",".");
            var img = new FileInfo(filePath);
            if (img.Exists)
            {
                var imgFile = img.OpenRead();
                Stream targetStream = context.Response.OutputStream;
                imgFile.CopyTo(targetStream);
                targetStream.Close();
                imgFile.Close();
            }
            else
            {
                context.Response.StatusCode = 404;
                context.Response.Close();
            }
        }

        private void ServeRSS(HttpListenerContext context)
        {


            WebClient webClient = new WebClient();
            var bouquetStream= webClient.OpenRead("http://" + DreamboxHost +"/web/getservices");
            //var bouquetStream= webClient.OpenRead("http://" + DreamboxHost +"/web/getallservices");
            XmlSerializer ser = new XmlSerializer(typeof(e2servicelist));
            var e2 = (e2servicelist)ser.Deserialize(bouquetStream);
            
            rss rss = new rss();
            
            var maxBouquets = 6;
            if (e2.Items.Length < maxBouquets)
            {
                maxBouquets = e2.Items.Length;
            }

            for (int i = 0; i < maxBouquets; i++)
            {
                GetBouquet(e2.Items[i], rss);
            }

            

            StringWriter sw= new StringWriter();

            XmlSerializer serializer = new XmlSerializer(typeof(rss));
            
            /*serializer.Serialize(sw,rss);
            var rssText = sw.ToString();
            var rssBytes = sw.Encoding.GetBytes(rssText);*/

            context.Response.ContentType = "application/rss+xml";
            //context.Response.ContentLength64 = rssBytes.Length;
            context.Response.AppendHeader("Last-Modified", DateTime.Now.ToString("r"));
            context.Response.AppendHeader("Accept-Ranges", "bytes");
            
            serializer.Serialize(context.Response.OutputStream, rss);
            //context.Response.OutputStream.Write(rssBytes,0,rssBytes.Length);
            context.Response.Close();
        }

        private void GetBouquet(e2servicelistrecursiveE2bouquet bouquet, rss rss)
        {
            WebClient webClient = new WebClient();
            var epgStream = webClient.OpenRead("http://" + DreamboxHost + "/web/epgnow?bRef=" + bouquet.e2servicereference);
            XmlSerializer epgSer = new XmlSerializer(typeof(e2eventlist));
            //StreamReader sr= new StreamReader("getallservices.xml");
            var epgData = (e2eventlist)epgSer.Deserialize(epgStream);




            rssChannel channel = new rssChannel();
            channel.title = bouquet.e2servicename;
            if (channel.title.Length>10)
            {
                channel.title = channel.title.Substring(0, 10) + "...";
            }

            foreach (var e2item in epgData.Items)
            {
                if (e2item.e2eventservicereference.Contains(e2item.e2eventservicename))
                {
                    continue;
                }
                rssChannelItem item = new rssChannelItem();
                item.title = e2item.e2eventtitle; //Sportschau
                item.description = e2item.e2eventdescription + Environment.NewLine + e2item.e2eventdescriptionextended;
                item.info = e2item.e2eventservicename;//Das Erste
                item.image = "http://" + DomainName +  "/picon/" + e2item.e2eventservicereference.Trim(':').Replace(':', '_') + ".jpg";
                item.link = "http://" + DomainName + ":" + ListeningPort + "/" + e2item.e2eventservicereference.Replace(':', '_') + ".flv";

                DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);

                // Add the number of seconds in UNIX timestamp to be converted.
                var start = GetInteger(e2item.e2eventstart);
                var duration = GetInteger(e2item.e2eventduration);
                var current = GetInteger(e2item.e2eventcurrenttime);
                var left = duration - current + start;


                if (left > 0)
                {
                    item.duration = TimeSpan.FromSeconds(left).ToString("g");
                }
                item.label.Add(new rssChannelItemLabel() { Value =  channel.title });
                channel.item.Add(item);
            }


            rss.channel.Add(channel);
        }

        private int GetInteger(string value)
        {
            int retVal;
            int.TryParse(value, out retVal);
            return retVal;
        }

        private void MonitorVLC()
        {
            try
            {
                WebRequest wr = WebRequest.Create("http://localhost:" + VLCPort + "/requests/status.xml");
                var response = wr.GetResponse();
                var responseStream = response.GetResponseStream();
                XDocument xdoc = XDocument.Load(responseStream);
                responseStream.Close();
                /*progressBar1.Maximum = int.Parse(xdoc.Descendants("length").First().Value);
                progressBar1.Value = int.Parse(xdoc.Descendants("time").First().Value);
                TaskbarItemInfo.ProgressValue = progressBar1.Value / progressBar1.Maximum;*/
                if (xdoc.Descendants("state").First().Value == "stop")
                {
                    //:/requests/status.xml?command=pl_play

                    _cancel = true;
                    //Cancel();
                }
            }
            catch (Exception)
            {
                _cancel = true;
                //Cancel();
            }
        }

        private bool _cancel = false;

        private void ServeVideo(HttpListenerContext context)
        {
            _cancel = false;
            if (!context.Request.Url.AbsolutePath.EndsWith("flv")) return;

            var tempSource= new FileInfo(Path.GetTempFileName());
            tempSource.MoveTo(Path.ChangeExtension(tempSource.FullName,"flv"));

            Process vlcProc = new Process();
            vlcProc.StartInfo.FileName = VLCPath;
            vlcProc.StartInfo.Arguments = "--extraintf=http --http-host=localhost:" + VLCPort + " http://" + DreamboxHost + ":8001/" + context.Request.Url.AbsolutePath.Replace(".flv", "").Replace("/", "").Replace('_', ':') + 
                " --sout \"#" + TranscodeProfile + ":file{dst=" + tempSource.FullName + "\"";
            //vlcProc.EnableRaisingEvents = true;
            //vlcProc.Exited += VLCProcess_Exited;
            vlcProc.Start();
            Thread.Sleep(8000);

            Stream sourceStream = tempSource.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

            Stream targetStream = context.Response.OutputStream;
            context.Response.ContentType = "video/x-flv";
            context.Response.ContentLength64 = 280166400;
            context.Response.AppendHeader("Last-Modified", DateTime.Now.ToString("r"));
            context.Response.AppendHeader("Accept-Ranges", "bytes");
            //Stream targetStream = new FileStream(@"d:\dump.flv", FileMode.Create, FileAccess.Write, FileShare.Read);
            byte[] buffer = new byte[1024];
            int count;
            count = sourceStream.Read(buffer, 0, 1024);

            var strHeader = Encoding.ASCII.GetString(buffer);
            int startIndex = GetStartIndex(strHeader, "duration"); //in seconds
            double duration = GetDouble(buffer, startIndex);
            duration = 60*60*1; //1 h
            SetDouble(buffer, duration, startIndex);
            startIndex = GetStartIndex(strHeader, "filesize");
            double filesize = GetDouble(buffer, startIndex); //in bytes
            filesize = 280166400;
            SetDouble(buffer, filesize, startIndex);
            targetStream.Write(buffer, 0, count);
            State = ServerState.Start;
            long sum = 0;
            while (!_cancel)
            {
                count = sourceStream.Read(buffer, 0, 1024);
                try
                {
                    if (count == 0)
                    {
                        Thread.Sleep(8000);
                        MonitorVLC();
                    }
                    else
                    {
                        targetStream.Write(buffer, 0, count);

                        sum += count;
                        BytesSent = sum;
                    }
                }
                catch (HttpListenerException ex)
                {
                    break;
                }

            }
            State = ServerState.Stop;
            sourceStream.Close();
            try
            {
                targetStream.Flush();
                context.Response.Close();
                targetStream.Close();
            }
            catch (Exception)
            {
            }

            if (!vlcProc.HasExited)
            {
                vlcProc.Kill();
                vlcProc.WaitForExit();
            }

            tempSource.Delete();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged!=null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}
